iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 14
0
IoT

制霸IoT 30Day!系列 第 14

Day 14 硬體與MQTT使用連結

  • 分享至 

  • xImage
  •  

MQTT 使用與連結

今天介紹 硬體的 MQTT 使用與連結實際操作部分,會有較多程式碼。

安裝程式庫

一般來說安裝的程式庫會介紹找Adafruit MQTT這種通用性的程式庫,今天我們介紹另一套
Async MQTT client for ESP8266 and ESP32這個程式庫。有何特點呢?

  • 專為 ESP8266 ESP32 實作
  • 非同步 I/O Async 不用特地去去延遲硬體做處理。
  • 因非同步反應速度飛快。

Async MQTT client for ESP8266 and ESP32

Async MQTT client for ESP8266 and ESP32

我們依樣在之前專案修改 platformio.ini 增加:

[env:nodemcuv2]
platform = espressif8266
board = nodemcuv2
framework = arduino

monitor_speed = 115200

lib_deps =
    ThingSpeak
    AsyncMqttClient

使用與連結範例

直接上完整範例檔案

#include <Arduino.h>
#include <Wire.h>

#include <ESP8266WiFi.h>
#include "secrets.h"
#include "aht1x.h"
#include <Ticker.h>
#include <AsyncMqttClient.h>
#include "ThingSpeak.h"

#define THINGSPEAK_DELAY 15000

uint8_t readBuffer[17] = {0};
uint8_t writeBuffer[3] = {0};
uint32_t timeout, loop_timeout;

float hum = 0;
float temp = 0;

const char *ssid = STASSID;
const char *password = STAPSK;

WiFiClient client;

unsigned long myChannelNumber = SECRET_CH_ID;
const char *myWriteAPIKey = SECRET_WRITE_APIKEY;

// Mqtt
#define MQTT_HOST IPAddress(192, 168, 0, 51)
#define MQTT_PORT 1883
AsyncMqttClient mqttClient;
Ticker mqttReconnectTimer;

WiFiEventHandler wifiConnectHandler;
WiFiEventHandler wifiDisconnectHandler;
Ticker wifiReconnectTimer;

void connectToWifi()
{
  Serial.println("Connecting to Wi-Fi...");
  WiFi.begin(ssid, password);
}

void connectToMqtt()
{
  Serial.println("Connecting to MQTT...");
  mqttClient.connect();
}

void onWifiConnect(const WiFiEventStationModeGotIP &event)
{
  Serial.println("Connected to Wi-Fi.");
  connectToMqtt();
}

void onWifiDisconnect(const WiFiEventStationModeDisconnected &event)
{
  Serial.println("Disconnected from Wi-Fi.");
  mqttReconnectTimer.detach(); // 先關閉Mqtt連線
  wifiReconnectTimer.once(2, connectToWifi);
}

//MQTT 連線後一系列的發送與接收 demo
void onMqttConnect(bool sessionPresent)
{
  Serial.println("Connected to MQTT.");
  Serial.print("Session present: ");
  Serial.println(sessionPresent);
  uint16_t packetIdSub = mqttClient.subscribe("test/day14", 2);
  Serial.print("Subscribing at QoS 2, packetId: ");
  Serial.println(packetIdSub);
  mqttClient.publish("test/lol", 0, true, "test 1");
  Serial.println("Publishing at QoS 0");
  uint16_t packetIdPub1 = mqttClient.publish("test/day14", 1, true, "test 2");
  Serial.print("Publishing at QoS 1, packetId: ");
  Serial.println(packetIdPub1);
  uint16_t packetIdPub2 = mqttClient.publish("test/day14", 2, true, "test 3");
  Serial.print("Publishing at QoS 2, packetId: ");
  Serial.println(packetIdPub2);
}

// MQTT 斷線
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason)
{
  Serial.println("Disconnected from MQTT.");

  if (reason == AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT)
  {
    Serial.println("Bad server fingerprint.");
  }

  if (WiFi.isConnected())
  {
    mqttReconnectTimer.once(2, connectToMqtt);
  }
}

// MQTT 訂閱
void onMqttSubscribe(uint16_t packetId, uint8_t qos)
{
  Serial.println("Subscribe acknowledged.");
  Serial.print("  packetId: ");
  Serial.println(packetId);
  Serial.print("  qos: ");
  Serial.println(qos);
}

// MQTT 取消訂閱
void onMqttUnsubscribe(uint16_t packetId)
{
  Serial.println("Unsubscribe acknowledged.");
  Serial.print("  packetId: ");
  Serial.println(packetId);
}

// MQTT 接收訊息
void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)
{
  Serial.println("Publish received.");
  Serial.print("  topic: ");
  Serial.println(topic);
  Serial.print("  qos: ");
  Serial.println(properties.qos);
  Serial.print("  dup: ");
  Serial.println(properties.dup);
  Serial.print("  retain: ");
  Serial.println(properties.retain);
  Serial.print("  len: ");
  Serial.println(len);
  Serial.print("  index: ");
  Serial.println(index);
  Serial.print("  total: ");
  Serial.println(total);
}

//MQTT 發布訊息
void onMqttPublish(uint16_t packetId)
{
  Serial.println("Publish acknowledged.");
  Serial.print("  packetId: ");
  Serial.println(packetId);
}

//溫濕度感測器 起始設定
void AHT10_setup()
{
  //AHT10 setup
  Wire.begin();
  Wire.beginTransmission(0x38);
  Wire.write(AHT1X_RESET);
  Wire.endTransmission();
  delay(AHT1X_RESET_DURATION);
  Wire.beginTransmission(0x38);
  writeBuffer[0] = AHT1X_INIT;
  writeBuffer[1] = AHT1X_INIT_DATA0;
  writeBuffer[2] = AHT1X_INIT_DATA1;
  for (int i = 0; i < AHT1X_CMD_SIZE; i++)
  {
    Wire.write(writeBuffer[i]);
  }
  Wire.endTransmission();
}

void setup()
{
  Serial.begin(115200);

  WiFi.mode(WIFI_STA);
  ThingSpeak.begin(client);

  mqttClient.onConnect(onMqttConnect);
  mqttClient.onDisconnect(onMqttDisconnect);
  mqttClient.onSubscribe(onMqttSubscribe);
  mqttClient.onUnsubscribe(onMqttUnsubscribe);
  mqttClient.onMessage(onMqttMessage);
  mqttClient.onPublish(onMqttPublish);
  mqttClient.setServer(MQTT_HOST, MQTT_PORT);
  wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect);
  wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect);
  AHT10_setup();
  loop_timeout = millis();
}

// 曲的溫濕度
void getTempHum()
{
  uint32_t xresult;
  Wire.beginTransmission(0x38);
  writeBuffer[0] = AHT1X_READ;
  writeBuffer[1] = AHT1X_READ_DATA0;
  writeBuffer[2] = AHT1X_READ_DATA1;
  for (int i = 0; i < AHT1X_CMD_SIZE; i++)
  {
    Wire.write(writeBuffer[i]);
  }
  Wire.endTransmission();
  delay(AHT1X_RH_MEASUREMENT_DELAY);
  Wire.requestFrom((uint8_t)0x38, (uint8_t)AHT1X_DATA_SIZE);

  timeout = millis() + DEFAULT_TIMEOUT;
  while (millis() < timeout)
  {
    if (Wire.available() < AHT1X_DATA_SIZE)
    {
      delay(AHT1X_RH_MEASUREMENT_DELAY / 4);
    }
    else
    {
      for (int i = 0; i < AHT1X_DATA_SIZE; i++)
      {
        readBuffer[i] = Wire.read();
      }
      xresult = (((uint32_t)readBuffer[1] << 16) | ((uint32_t)readBuffer[2] << 8) | (uint32_t)readBuffer[3]) >> 4;
      hum = (float)xresult;
      hum *= 100;
      hum /= 1048576;
      xresult = (((uint32_t)readBuffer[3] & 0x0F) << 16) | ((uint32_t)readBuffer[4] << 8) | (uint32_t)readBuffer[5];
      temp = (float)xresult;
      temp *= 200;
      temp /= 1048576;
      temp -= 50;
    }
  }
}

// the loop function runs over and over again forever
void loop()
{
  if (millis() > loop_timeout && WiFi.status() == WL_CONNECTED)
  {
    getTempHum();
    if ((temp > -50) && (hum > 0))
    {
      Serial.println(String("") + "Humidity(%RH):\t\t" + hum + "%");
      Serial.println(String("") + "Temperature(C):\t" + temp + " C");
      ThingSpeak.setField(1, temp);
      ThingSpeak.setField(2, hum);
      int x = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
      if (x == 200)
      {
        Serial.println("Channel update successful.");
      }
      else
      {
        Serial.println("Problem updating channel. HTTP error code " + String(x));
      }
    }
    loop_timeout = millis() + THINGSPEAK_DELAY;
  }
}

這個範例會先訂閱然後執行三種發布訊息範例。
上傳後會看到輸出範例:

Connected to Wi-Fi.
Connecting to MQTT...
Connected to MQTT.
Session present: 0
Subscribing at QoS 2, packetId: 1
Publishing at QoS 0
Publishing at QoS 1, packetId: 2
Publishing at QoS 2, packetId: 3
Publish acknowledged.
  packetId: 2
Subscribe acknowledged.
  packetId: 1
  qos: 2
Publish received.
  topic: test/day14
  qos: 2
  dup: 0
  retain: 0
  len: 6
  index: 0
  total: 6
Publish received.
  topic: test/day14
  qos: 1
  dup: 0
  retain: 0
  len: 6
  index: 0
  total: 6
Publish received.
  topic: test/day14
  qos: 2
  dup: 0
  retain: 0
  len: 6
  index: 0
  total: 6
Publish acknowledged.
  packetId: 3

而且我們去 上一篇介紹的 EMQ X 管理介面可以看到連線的裝置!
http://PI ip:18083/#/connections

EMQ X 管理介面

EMQ X 管理介面

將溫濕度資料一併發布

這邊示範我們除了將濕度資料送到 ThingSpeak 同時透過 MQTT 發布到我們的主機 MQTT Broker。

程式碼範例與上方相同只有發送區塊不同:

void loop()
{
  if (millis() > loop_timeout && WiFi.status() == WL_CONNECTED)
  {
    getTempHum();
    if ((temp > -50) && (hum > 0))
    {
      Serial.println(String("") + "Humidity(%RH):\t\t" + hum + "%");
      Serial.println(String("") + "Temperature(C):\t" + temp + " C");
      ThingSpeak.setField(1, temp);
      ThingSpeak.setField(2, hum);
      int x = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
      if (x == 200)
      {
        Serial.println("Channel update successful.");
      }
      else
      {
        Serial.println("Problem updating channel. HTTP error code " + String(x));
      }
      char buff[20];
      mqttClient.publish("home/temp", 1, true, dtostrf(temp, 8, 6, buff));
      mqttClient.publish("home/hum", 1, true, dtostrf(hum, 8, 6, buff));
    }
    loop_timeout = millis() + THINGSPEAK_DELAY;
  }
}

資料發送到:

  • home/temp 溫度
  • home/hum 濕度

上傳後會看到輸出範例:

Humidity(%RH):          62.23%
Temperature(C): 28.75 C
Channel update successful.
Publish acknowledged.
  packetId: 90
Publish acknowledged.
  packetId: 91
Humidity(%RH):          62.24%
Temperature(C): 28.75 C
Channel update successful.
Publish acknowledged.
  packetId: 92
Publish acknowledged.
  packetId: 93
Humidity(%RH):          62.30%
Temperature(C): 28.75 C
Channel update successful.
Publish acknowledged.
  packetId: 94
Publish acknowledged.
  packetId: 95

那我們用電腦去連線 MQTT 訂閱:
我們訂閱 home/# 這樣可以直接收到兩個。

mqtt 溫度

mqtt 溫度

mqtt 濕度

mqtt 濕度

結語

今天介紹 MQTT 硬體實際使用與介紹,硬體以往要將資料發送到電腦端是需要具備很煩瑣的資料處理與連接那透過無線網路大大降低了便利性與難度。

Blog 同步刊登


上一篇
Day 13 MQTT使用與連結
下一篇
Day 15 圖表(一)
系列文
制霸IoT 30Day!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
eric19740521
iT邦新手 1 級 ‧ 2020-02-25 08:38:56

怎麼不用esp32去實作???

MqttClient這部分,我會寫在php服務器裡面.
這樣esp32程式碼會輕巧些

SDpower iT邦新手 5 級 ‧ 2020-02-25 13:08:03 檢舉

以ESP32 作為MqttClient很多好處,直接持續連線有斷線很快可以知道。你再多做一層用PHP接http送只是多麻煩且HTTP 交握程序太多比起一般tcp 繁瑣。

1.你的回答很棒!!作為一個設備偵測,使用mqtt快速回應,這點我倒沒想到

2.另外想請教
mqtt 如果設備是訂閱的.那如果設備沒上線,是否能有一個方法,讀取之前,沒收到的訂閱訊息

感謝你的回答

我要留言

立即登入留言